package server;

import java.net.*;
import java.io.*;
import java.util.*;

import link.ReceivedBuffer;

class CentralServer {

	ReceivedBuffer rb;
	HashMap<String, List<String>> neighbour_table;
	/** Id <-> Neighbour */
	HashMap<String, String> table;
	DatagramSocket sender;
	int in_port;
	int out_port;
	public static Timer topoDestroy;
	public static final long TOPOTIME = 10000;

	public CentralServer() {
		in_port = 11111;
		out_port = 11111;
		try {
			rb = new ReceivedBuffer();
			neighbour_table = new LinkedHashMap<String, List<String>>(30);
			table = new LinkedHashMap<String, String>(30);

			Thread t_s = new Thread(new InServer(rb));
			t_s.start();
			sender = new DatagramSocket();
		} catch (SocketException e) {
			System.out.println(e);
			System.exit(-1);
		}
	}

	public CentralServer(int in, int out) {
		in_port = in;
		out_port = out;
		try {
			rb = new ReceivedBuffer();
			neighbour_table = new LinkedHashMap<String, List<String>>(30);
			table = new LinkedHashMap<String, String>(30);

			Thread t_s = new Thread(new InServer(rb, in_port));
			t_s.start();
			sender = new DatagramSocket();
		} catch (SocketException e) {
			System.out.println(e);
			System.exit(-1);
		}
	}

	void addRoute(String from, String to) {
		if (!table.containsKey(from)) {
			System.out.println("addRoute: unknown from : " + from);
		} else if (!table.containsKey(to)) {
			System.out.println("addRoute: unknown to : " + to);
		} else {
			// WARNING: there is no checking if the link
			// from -> to was already there
			List<String> tos = neighbour_table.get(from);
			tos.add(to);
			tos = neighbour_table.get(to);
			tos.add(from);
			System.out.println("Route added : " + from + " -> " + to);
		}
	}

	void delRoute(String from, String to) {
		if (!table.containsKey(from)) {
			System.out.println("delRoute: unknown from : " + from);
		} else if (!table.containsKey(to)) {
			System.out.println("delRoute: unknown to : " + to);
		} else {
			List<String> tos = neighbour_table.get(from);
			tos.remove(to);
			tos = neighbour_table.get(to);
			tos.remove(from);
			System.out.println("Route deleted : " + from + " -> " + to);
		}
	}

	void insertNode(String from, String real_name) {
		if (table.containsKey(from) && table.get(from).equals(real_name)) {
			System.out.println("Node " + from + "(" + real_name
					+ ") already known, nothing changed");
		} else {
			table.put(from, real_name);
			neighbour_table.put(from, new LinkedList<String>());
			List<String> l = neighbour_table.get(from);
			l.add(from);
			System.out.println("Node " + from + "(" + real_name
					+ ") added to the network");
		}
	}

	void showTopology() {
		System.out.println("Ids -> Computer:");
		for (Map.Entry<String, String> e : table.entrySet()) {
			System.out.println("\t" + e.getKey() + " -> " + e.getValue());
		}
		System.out.println("\nTopology:");
		for (Map.Entry<String, List<String>> e : neighbour_table.entrySet()) {
			System.out.println(e.getKey() + ":");
			for (String to : e.getValue()) {
				System.out.println("\t" + to);
			}
		}

	}

	void beginDestroyingTheGraph(){
		System.out.println("Starting the graph destruction");
		topoDestroy = new Timer();
		topoDestroy.schedule(new TopoDestroyer(20, 40), 1000, TOPOTIME);
	}
	
	void stopDestroyingTheGraph(){
		// We won't recover the deleted links, I should switch to a thread for this...
		System.out.println("Stopping the graph destruction");
		topoDestroy.cancel();
		topoDestroy = new Timer();
	}
	
	void route() {
		try {
			while (true) {
				String msg = rb.receive();
				if (msg == null) {
					Thread.sleep(100);
				} else {
					StringTokenizer st = new StringTokenizer(msg, ":");
					if (st.countTokens() < 2) {
						System.out.println("Error, msg <" + msg + ">("
								+ msg.length()
								+ ") must be shaped as\"to:from:data\"");
					} else {
						String dest = st.nextToken();
						String from = st.nextToken();
						String type = st.nextToken().toLowerCase();
						if (dest.toLowerCase().equals("server")) {
							if (type.equals("addroute")) {
								from = st.nextToken();
								dest = st.nextToken();
								addRoute(from, dest);
							} else if (type.equals("delroute")) {
								from = st.nextToken();
								dest = st.nextToken();
								delRoute(from, dest);
							} else if (type.equals("serverjoin")) {
								insertNode(from, st.nextToken());
							} else if (type.equals("topo")) {
								showTopology();
							} else if(type.equals("random")) {
								beginDestroyingTheGraph();
							} else if(type.equals("stoprandom")){
								stopDestroyingTheGraph();
							}
						} else if (!table.containsKey(from)) {
							System.out.println("Error, unknown sender : "
									+ from);
						} else {
							try {
								List<String> tos = neighbour_table.get(from);
								for (String to : tos) {
									byte[] buf = msg.getBytes();
									InetAddress real_dest = InetAddress
											.getByName(table.get(to));
									DatagramPacket pack = new DatagramPacket(
											buf, buf.length, real_dest,
											out_port);

									sender.send(pack);
								}
							} catch (UnknownHostException e) {
								System.out.println(e);
							} catch (IOException e) {
								System.out.println(e);
							}
						}
					}
				}
			}
		} catch (InterruptedException e) {
			System.out.println(e);
			System.exit(-1);
		}

	}

	public static void main(String[] args) {
		Main.c = new CentralServer();
		topoDestroy = new Timer();
		Main.c.route();
	}
}
